package base.thread_JUC.demo04;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * MYNOTICE 读写锁使用
 *
 * ReentrantLock 多线程同时读写一个共享资源类没有任何问题，但是它写的同时禁止了读，读的时候禁止了写，并发效率相对较弱，所以为了满足并发量，读取共享
 * 资源应该可以同时进行。
 * 但是，如果有一个线程想去写共享资源，就不应该再有其他线程可以对该资源进行读或写。
 * 小结：
 *      读-读能共存
 *      读-写不能共存
 *      写-写不能共存
 *
 *      写操作：原子+独占，整个过程必须是一个完整的统一体，中间不许被打断.
 */
public class Client {
    public static void main(String[] args) {
//        MyCache0 cache = new MyCache0();// 独占锁演示
        MyCache cache = new MyCache();// 读写锁演示
        // 10个线程写入
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(()->{
                cache.put(temp+"",temp+"");

            }, String.valueOf(i)).start();
        }

        // 10 个线程读取
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(()->{
                cache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义一个缓存类：相对 MyCache0 来说效率更高
 *
 * 此类的多线程运行效果如下：
 * t1 正在写入...1
 * 这个中间不会被打断
 * t1 完成写入
 *
 * t1 正在读取缓存...
 * 这个中间可以被打断
 * t3 正在读取缓存...
 * t3 完成读取缓存：3
 * t1 完成读取缓存：1
 *
 * t2 正在写入...2
 * 这个中间不会被打断
 * t2 完成写入
 *
 */
class MyCache{

    /**
     * volatile 缓存类的标配，可见性
     */
    private volatile Map<String, Object> map = new HashMap<>();

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * put 方法是一个原子性方法，每个线程的"写入"和"完成写入"操作是一个整体，中间必须不能被其它线程打断
     */
    public void put(String key, Object value) {
        try {
            // 上写锁
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"\t 正在写入。。。"+ key);
            map.put(key, value);
            // 模拟put方法是个延时操作
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t 完成写入");
        }finally {
            // 解锁
            lock.writeLock().unlock();
        }
    }

    /**
     * get 方法不是一个原子性方法，每个线程在读取时，允许其它线程来读取，中间可以被打断
     */
    public void get(String key) {
        try {
            // 上读锁
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"\t 正在读取缓存...");
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t 完成读取缓存："+ result);
        }finally {
            // 解锁
            lock.readLock().unlock();
        }
    }

    public void clear () {
        map.clear();
    }
}

/**
 * 使用 ReentrantLock 锁实现的一个低效率的缓存类.
 * 这个缓存类的 get 方法是一个原子性方法，其实get只是读取资源，对于缓存类来说，put写操作有必要保证原子性，而get就没有必要了。但是此类
 * 却给了get方法原子性操作，相对可读写锁来说效率降低了。
 *
 * 此类的多线程运行效果如下：
 * t1 正在写入...1
 * 这个中间不会被打断
 * t1 完成写入
 *
 * t1 正在读取缓存...
 * 这个中间不会被打断
 * t1 完成读取缓存：1
 */
class MyCache0 {
    private volatile Map<String, Object> map = new HashMap<>();

    private Lock lock = new ReentrantLock(false);

    public void put(String key, Object value) {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"\t 正在写入。。。"+ key);
            map.put(key, value);
            // 模拟put方法是个延时操作
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t 完成写入");
        }finally {
            lock.unlock();
        }
    }

    public void get(String key) {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"\t 正在读取缓存...");
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t 完成读取缓存："+ result);
        }finally {
            lock.unlock();
        }
    }

    public void clear () {
        map.clear();
    }
}